Skip to content

feat: add slash commands for agent switching#2790

Open
dgageot wants to merge 4 commits into
docker:mainfrom
dgageot:feat/agent-switching-commands
Open

feat: add slash commands for agent switching#2790
dgageot wants to merge 4 commits into
docker:mainfrom
dgageot:feat/agent-switching-commands

Conversation

@dgageot
Copy link
Copy Markdown
Member

@dgageot dgageot commented May 13, 2026

Summary

Adds support for slash commands that switch the active agent. For example, declaring /plan in the agent config makes the planner sub-agent take over the conversation when the user types /plan.

Usage

A new agent: field can be set on a command in agent.yaml:

agents:
  root:
    sub_agents: [planner, reviewer]
    commands:
      plan:
        description: Hand off to the planner
        agent: planner
      review:
        description: Hand off to the reviewer
        agent: reviewer

When the user types /plan, the active agent is switched to planner. Anything typed after the command (e.g. /plan add a logout button) is forwarded to the target agent as the first user message.

agent: can be combined with instruction: to switch and send a fixed prompt; on its own it acts as a pure handoff.

A complete example lives in examples/agent_switching_commands.yaml.

Changes

  • pkg/config/types/commands.go — new Agent field on Command; YAML parser accepts the agent key.
  • pkg/runtime/commands.go — new LookupCommand helper; ResolveCommand forwards trailing args verbatim for agent-only commands.
  • pkg/app/app.go — exposes App.LookupCommand.
  • pkg/tui/handlers.gohandleAgentCommand switches the active agent before sending the resolved message.
  • pkg/cli/runner.goPrepareUserMessage does the same for the non-TUI flow.
  • agent-schema.json — documents the new agent property.
  • examples/agent_switching_commands.yaml — full example with /plan, /review, /back.
  • pkg/runtime/commands_test.go — unit tests for the new behavior.

Validation

  • task lint clean.
  • task test passes for all changed packages.
  • task build succeeds.

@dgageot dgageot requested a review from a team as a code owner May 13, 2026 14:01
trungutt
trungutt previously approved these changes May 13, 2026
docker-agent

This comment was marked as outdated.

Comment thread pkg/cli/runner.go
Comment thread agent-schema.json
Comment thread pkg/tui/handlers.go
Comment thread pkg/cli/runner.go
Comment thread pkg/config/types/commands.go
dgageot added 3 commits May 18, 2026 10:28
Add support for commands that switch the active agent via a new 'agent:' field
in the commands section of agent.yaml. Users can now use /plan to switch to a
planner sub-agent, /review to switch to a reviewer, etc. Trailing arguments
after the command are forwarded to the target agent as the first prompt.
Wires the new agent-switching slash command feature into the built-in coder agent:
- Adds a planner sub-agent with read-only tools (filesystem, fetch, todo, user_prompt)
- Adds /plan command on root agent to switch to plan mode
- Adds symmetric /back command on planner agent to hand work back
- Updates root instruction to mention the /plan command
Addresses review feedback from docker-agent bot:

1. In pkg/cli/runner.go (PrepareUserMessage):
   - Moved ResolveCommand call BEFORE SetCurrentAgent
   - This ensures the command is looked up in the original agent's command table
   - Prevents raw slash-command strings from being sent to target agents when
     the target agent doesn't have the command defined

2. In pkg/tui/handlers.go (handleAgentCommand):
   - Added switchSucceeded flag to track whether agent switch was successful
   - Only send resolved message if the agent switch succeeded
   - Prevents messages from being sent to the wrong agent when switching fails
@dgageot dgageot force-pushed the feat/agent-switching-commands branch from 7df0e5a to 7e3d57d Compare May 18, 2026 08:41
@dgageot
Copy link
Copy Markdown
Member Author

dgageot commented May 18, 2026

/review

docker-agent

This comment was marked as outdated.

This commit addresses all review feedback from PR docker#2790:

**Critical fixes (blocking issues):**

1. **Fix SetCurrentAgent error handling in CLI and TUI** (runner.go, handlers.go)
   - If SetCurrentAgent fails, we now return an empty message instead of
     proceeding to send the message to the wrong agent
   - Added checked type assertion in TUI to prevent potential panics
   - Both CLI and TUI now properly handle agent switch failures

2. **Add unit tests for PrepareUserMessage and agent switching** (runner_test.go)
   - Added comprehensive tests for agent-switching commands
   - Tests cover success cases, failure cases, and edge cases
   - Tests verify that empty messages are returned on switch failures
   - Tests verify that agent-only commands (no instruction) work correctly

**Non-blocking improvements:**

3. **Update agent-schema.json** (agent-schema.json)
   - Changed description from 'must be reachable from sub-agent graph' to
     'must be defined in team configuration' to match runtime behavior
   - The runtime validates agent existence but doesn't enforce graph reachability

4. **Add explanatory comment for fallback logic** (commands.go)
   - Added comment explaining why description falls back to instruction
   - Clarifies the shorthand command definition behavior

5. **Improve code quality**
   - Fixed unchecked type assertion in TUI handlers
   - Used slog.WarnContext instead of slog.Warn for proper context propagation
   - All linters pass, all tests pass

Addresses feedback from @aheritier and docker-agent bot.
@dgageot dgageot requested a review from aheritier May 18, 2026 08:59
Copy link
Copy Markdown

@docker-agent docker-agent left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Assessment: 🔴 CRITICAL

Agent-switching slash commands are a well-structured addition. The runtime layer, schema, tests, and TUI path are all clean. One real bug found in the non-TUI (CLI) path: when agent switching fails, the error is signaled via an empty session.UserMessage that the caller unconditionally adds to the session and forwards to the LLM, silently corrupting conversation history.

Comment thread pkg/cli/runner.go
// to the wrong agent. Return an empty message to signal the error.
if err := rt.SetCurrentAgent(cmd.Agent); err != nil {
slog.WarnContext(ctx, "Failed to switch agent for /command", "agent", cmd.Agent, "error", err)
return session.UserMessage(""), ""
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[HIGH] Empty message silently added to session and forwarded to LLM on agent-switch failure

PrepareUserMessage returns session.UserMessage("") here to "signal" the error, but the caller in Run (line 107) unconditionally does:

userMsg, attachedPath := PrepareUserMessage(ctx, rt, userInput, cfg.AttachmentPath)
sess.AddMessage(userMsg)   // no guard — empty message is added
rt.RunStream(ctx, sess)    // LLM receives empty user message in history

There is no nil/empty check at the call site. The LLM will receive a blank user turn in the conversation history, which can confuse the model and silently corrupt the session. The TUI path (handleAgentCommand) correctly suppresses the message on failure — the CLI path needs the same treatment.

Suggested fix — return error explicitly so callers are forced to handle it:

func PrepareUserMessage(...) (*session.Message, string, error) {
    ...
    if err := rt.SetCurrentAgent(cmd.Agent); err != nil {
        slog.WarnContext(ctx, "Failed to switch agent for /command", ...)
        return nil, "", fmt.Errorf("switch agent %q: %w", cmd.Agent, err)
    }
    ...
    return msg, attachPath, nil
}

Then the caller can continue / surface the error instead of forwarding an empty message.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants